<html>
<head>
<title>Encrypted File Bundle</title>
<script type="text/javascript">
function decrypt_action() {
	/* Hide the link while decrypting */
	document.getElementById('download-link').style.display = 'none';

	/* Decrypt the data */
	var data = "%s";
	data = atob(data);

	var key = document.getElementById('key').value;
	if(key.length > 32) { key = key.substring(0, 32); }
	while(key.length < 32) { key = key + '\0'; }

	data = decrypt_strings(data, key).slice(0, %d);

	/* Generate and show the download link */
	if(is_ie_user()) {
		/* For IE Data URI doesn't work use a Blob */
		var decrypt_buffer = new ArrayBuffer(data.length);
		var decrypt_array = new Uint8Array(decrypt_buffer);

		for(var i = 0; i < data.length; i++) {
			decrypt_array[i] = data.charCodeAt(i);
		}

		var bb = MSBlobBuilder();
		bb.append(decrypt_array);
		download_blob = bb.getBlob();
		document.getElementById('ie-download-link').style.display = 'inline';
	} else {
		/* Generate a data uri for all other browsers */
		document.getElementById('download-link').href = "data:application/octet-stream;charset=utf-8;base64," + btoa(data);
		document.getElementById('download-link').style.display = 'inline';
	}
}

function is_ie_user() {
	var userAgent = navigator.userAgent;
	    var msie = userAgent.indexOf('MSIE ');
	    var trident = userAgent.indexOf('Trident/');
	    return msie > 0 || trident > 0;
}

function ie_download() {
	window.navigator.msSaveOrOpenBlob(download_blob, '%s');
}

/***************************************/
/* AES Encryption functions begin here */
/***************************************/

/* Doubles a number in the Galois Field */
function GF_Double(n) { if (n >= 128) { return (n << 1) ^ 0x11B } else { return n << 1 } }

/* Returns 2^i in the Galois Field (Precalculated) */
var rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
	0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5];

/* Multiplies two numbers in the Galois Field.  Uses bit shifting tricks since they are two polynomials */
function GF_Multiply(a, b) {
	var accum = 0;
	while (a > 0) {
		if (a & 1) { accum ^= b }
		b = GF_Double(b);
		a >>>= 1;
	}
	return accum;
}

/* SBox function (Precalculated, since it is computationally expensive and always the same for any given value) */
var sbox = [
	0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
	0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
	0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
	0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
	0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
	0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
	0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
	0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
	0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
	0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
	0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
	0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
	0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
	0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
	0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
	0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
];

/* Reverse SBox function (Precalculated, since it is computationally expensive and always the same for any given value) */
var reverse_sbox = [
	0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
	0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
	0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
	0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
	0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
	0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
	0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
	0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
	0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
	0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
	0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
	0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
	0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
	0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
	0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
	0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
	];

/* Performs an XOR or each byte in a with the corresponding byte in b.
 * The result will be as long as the shorter of the two lists. */
function add_bytes(a, b) {
	var c = new Array(Math.min(a.length, b.length));
	for (var i = 0; i < c.length; i++) {
		c[i] = a[i] ^ b[i];
	}	
	return c
}

/* Rotate the array of bytes forward reps repetitions.
 * Thus for each repetition the first byte will move to the end of the array.
 * reps can be negative to perform a reverse rotate. */
function rotate_bytes(b) { return [b[1], b[2], b[3], b[0]]; }

/* The AES key expansion */
function key_expansion(key) {
	var max_bytes = key.length * 4 + 112;
	var block_size = key.length;
	var rcon_iter = 1;
	var exkey = key.slice();

	while(exkey.length < max_bytes) {
		var temp = rotate_bytes(exkey.slice(-4));
		temp = temp.map(function x(val) { return sbox[val] });
		temp[0] ^= rcon[rcon_iter++];
		exkey.push.apply(exkey, add_bytes(temp, exkey.slice(-block_size)));

		for(var i = 0; i < 3; i++) { exkey.push.apply(exkey, add_bytes(exkey.slice(-4), exkey.slice(-block_size))); }
		
		if (block_size == 24) {
			for(var i = 0; i < 2; i++) { exkey.push.apply(exkey, add_bytes(exkey.slice(-4), exkey.slice(-block_size))); }
		}

		if (block_size == 32) {
			var temp = (exkey.slice(-4));
			temp = temp.map(function x(val) { return sbox[val] });
			exkey.push.apply(exkey, add_bytes(temp, exkey.slice(-block_size)));

			for(var i = 0; i < 3; i++) { exkey.push.apply(exkey, add_bytes(exkey.slice(-4), exkey.slice(-block_size))); }
		}
	}

	return exkey.slice(0, max_bytes);
}

/* Given a 4 byte column, perform the appropriate matrix multiplication in the Galois Field */
function reverse_mix_column(a) {
	return [
		GF_Multiply(14, a[0]) ^ GF_Multiply(11, a[1]) ^ GF_Multiply(13, a[2]) ^ GF_Multiply( 9, a[3]),
		GF_Multiply( 9, a[0]) ^ GF_Multiply(14, a[1]) ^ GF_Multiply(11, a[2]) ^ GF_Multiply(13, a[3]),
		GF_Multiply(13, a[0]) ^ GF_Multiply( 9, a[1]) ^ GF_Multiply(14, a[2]) ^ GF_Multiply(11, a[3]),
		GF_Multiply(11, a[0]) ^ GF_Multiply(13, a[1]) ^ GF_Multiply( 9, a[2]) ^ GF_Multiply(14, a[3])
	];
}

/* Main steps in decryption */
function reverse_sub_bytes(state) { return state.map(function x(val) { return reverse_sbox[val] }); }

function reverse_shift_rows(state) {
	return [
		state[ 0], state[ 1], state[ 2], state[ 3],
		state[ 7], state[ 4], state[ 5], state[ 6],
		state[10], state[11], state[ 8], state[ 9],
		state[13], state[14], state[15], state[12]
	]
}

function transpose(block) {
	var result = [];
	for(var col = 0; col < 4; col++) {
		for(var row = 0; row < 4; row++) {
			result[(4*row)+col] = block[(4*col)+row];
		}
	}
	return result;
}

function reverse_mix_columns(state) {
	var temp = transpose(state);
	var result = [];
	result.push.apply(result, reverse_mix_column(temp.slice( 0, 4)));
	result.push.apply(result, reverse_mix_column(temp.slice( 4, 8)));
	result.push.apply(result, reverse_mix_column(temp.slice( 8,12)));
	result.push.apply(result, reverse_mix_column(temp.slice(12,16)));
	return transpose(result);
}

/* Main decryption function */
function decrypt_data(data, key) {
	var num_rounds = key.length / 4 + 6;

	var plain_data = [];
	var exkey = key_expansion(key);
	var cbc = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
	
	/* Work in 16 byte chunks */
	for (var offset = 0; offset < data.length; offset += 16) {
		var state = data.slice(offset, offset + 16);

		/* If we reach the end of the data, pad the array
		 * with 0's to ensure it is 16 bytes */
		while(state.length < 16) { state.push(0); }

		/* Initial Round */
		var round_key = exkey.slice(-16);
		state = add_bytes(round_key, state);
		
		/* Main Rounds */
		for(var round = 0; round < num_rounds - 1; round++) {
			round_key = exkey.slice(-round*16-32, -round*16-16);
			state = reverse_mix_columns(add_bytes(round_key, reverse_sub_bytes(reverse_shift_rows(state))));
		}


		/* Final Round */
		round_key = exkey.slice(0, 16);
		state = add_bytes(round_key, reverse_sub_bytes(reverse_shift_rows(state)));

		state = add_bytes(cbc, state);
		cbc = data.slice(offset, offset + 16);
		plain_data.push.apply(plain_data, state);
	}

	return plain_data;
}

/* Wraps decrypt_data to take string parameters and return a string
 * whereas decrypt_data takes and returns arrays of numbers */
function decrypt_strings(data, key) {
	var data_array = [];
	for(var i = 0; i < data.length; i++) {
		data_array[i] = data.charCodeAt(i);
	}

	var key_array = []
	for(var i = 0; i < key.length; i++) {
		key_array[i] = key.charCodeAt(i) & 0xff;
	}

	data_array = decrypt_data(data_array, key_array);

	var decrypted = ""
	for(var i = 0; i < data_array.length; i++) {
		decrypted += String.fromCharCode(data_array[i])
	}

	return decrypted; 
}

</script>

</head>
<body>
<h2 style="margin-bottom: 0;">Encrypted File Bundle</h2>
<p style="margin-top: 0; font-size: small;">(<a href="https://code.google.com/p/encrypted-html-bundle/">Source</a>)</p>
<p>Enter passphrase:</p>
<input id="key" type="password" />
<input type="submit" value="Decrypt!" onclick="decrypt_action()" />

<p>
<a id="download-link" style="display: none;" href="" download="%s">Download</a>
<a id="ie-download-link" style="display: none;" href="" onclick="ie_download();">Download</a>
</p>
</body>
</html>
